/**
 * Copyright 2012 Brigham Young University
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.byu.nlp.data;

import java.util.Comparator;
import java.util.Iterator;

import com.google.common.collect.AbstractIterator;

import edu.byu.nlp.data.SparseFeatureVectors.ValueFunction;


/**
 * @author rah67
 *
 */
public class SparseFeatureVector {

	public static interface Entry {
		int getIndex();
		double getValue();
	}
	
	public static interface IndexVisitor {
		void visitIndex(int index);
	}
	
	public static interface EntryVisitor {
		void visitEntry(int index, double value);
	}
	
	private class BasicEntry implements Entry {
		private final int index;
		private final double value;
		
		public BasicEntry(int index, double value) {
			this.index = index;
			this.value = value;
		}

		@Override
		public int getIndex() {
			return index;
		}

		@Override
		public double getValue() {
			return value;
		}
		
	}
	
	private final int[] indices;
	private final double[] values;

	public SparseFeatureVector(int[] indices, double[] values) {
		this.indices = indices;
		this.values = values;
	}

	public double dotProduct(double[] v) {
		double dotProduct = 0.0;
		for (int i = 0; i < values.length; i++) {
			dotProduct += values[i] * v[indices[i]];
		}
		return dotProduct;
	}
	
	public SparseFeatureVector copy() {
		return new SparseFeatureVector(indices.clone(), values.clone());
	}
	
	void transformValues(ValueFunction f) {
		for (int i = 0; i < values.length; i++) {
			values[i] = f.apply(indices[i], values[i]);
		}
	}
	
	public void visitIndices(IndexVisitor v) {
		for (int index : indices) {
			v.visitIndex(index);
		}
	}
	
	public void visitSparseEntries(EntryVisitor v) {
		for (int i = 0; i < indices.length; i++) {
			v.visitEntry(indices[i], values[i]);
		}
	}
	
	private final class SparseEntryIterator extends AbstractIterator<Entry> {
		
		private int i = 0;

		@Override
		protected Entry computeNext() {
			if (i >= indices.length) {
				return super.endOfData();
			}
			Entry e = new BasicEntry(indices[i], values[i]);
			++i;
			return e;
		}
	}

	public Iterable<Entry> sparseEntries() {
		return new Iterable<Entry>() {

			@Override
			public Iterator<Entry> iterator() {
				return new SparseEntryIterator();
			}
			
		};
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[ ");
		for (int i = 0; i < indices.length; i++) {
			if (i > 0) {
				sb.append(", ");
			}
			sb.append(indices[i]);
			sb.append(':');
			sb.append(values[i]);
		}
		sb.append(" ]");
		return sb.toString();
	}
	
	public static Comparator<Entry> valueComparator() {
		return new Comparator<Entry>() {

			@Override
			public int compare(Entry entry1, Entry entry2) {
				return Double.compare(entry1.getValue(), entry2.getValue());
			} 
			
		};
	}

	public double sum() {
		double sum = 0.0;
		for (double v : values) {
			sum += v;
		}
		return sum;
	}

	/**
	 * Computes A + v^T x this and stores the result in A. 
	 */
	public void preMultiplyAndAddTo(double[] v, double[][] A) {
		for (int i = 0; i < indices.length; i++) {
			int column = indices[i];
			double value = values[i];
			for (int row = 0; row < v.length; row++) {
				A[row][column] += v[row] * value;
			}
		}
	}
	
	/**
	 * Computes A + v^T x this and stores the result in A, where A is a column-major matrix starting at offset off. 
	 */
	public void preMultiplyAndAddTo(double[] v, double[] A, int offset) {
		for (int i = 0; i < indices.length; i++) {
			int column = indices[i];
			double value = values[i];
			int index = offset + v.length * column;
			for (int row = 0; row < v.length; row++) {
				A[index++] += v[row] * value;
			}
		}
	}	

	/**
	 * Computes v^T + A x this^T and stores the results in v 
	 */
	public void preMultiplyAsColumnAndAddTo(double[][] A, double[] v) {
		for (int i = 0; i < indices.length; i++) {
			int index = indices[i];
			double value = values[i];
			for (int row = 0; row < A.length; row++) {
				v[row] += A[row][index] * value; 
			}
		}
	}
	
	/**
	 * Computes v^T + A x this^T and stores the results in v, where A is a column-major matrix with numColumns,
	 * starting at offset off.
	 */
	public void preMultiplyAsColumnAndAddTo(double[] A, int offset, double[] v) {
		for (int i = 0; i < indices.length; i++) {
			int column = indices[i];
			double value = values[i];
			int index = offset + v.length * column;
			for (int row = 0; row < v.length; row++) {
				v[row] += A[index++] * value; 
			}
		}
	}

	/**
	 * Computes this + v and stores the result in v (vector addition)
	 */
	public void addTo(double[] v) {
		for (int i = 0; i < indices.length; i++) {
			v[indices[i]] += values[i];
		}
	}

	public void subtractFrom(double[] v) {
		for (int i = 0; i < indices.length; i++) {
			v[indices[i]] -= values[i];
		}
	}

}
